/**
 * $Id: Graph.js,v 1.45 2012-08-02 13:56:37 gaudenz Exp $ Copyright (c)
 * 2006-2012, JGraph Ltd
 */
/**
 * Constructs a new graph instance. Note that the constructor does not take a
 * container because the graph instance is needed for creating the UI, which in
 * turn will create the container for the graph. Hence, the container is
 * assigned later in EditorUi.
 */


Graph = function(container, model, renderHint, stylesheet) {
	mxGraph.call(this, container, model, renderHint, stylesheet);

	this.setConnectable(true);
	this.setDropEnabled(true);
	this.setPanning(true);
	this.setTooltips(true);
	this.setAllowLoops(true);
	this.allowAutoPanning = true;
	this.resetEdgesOnConnect = false;
	this.constrainChildren = false;
	
	// Do not scroll after moving cells
	this.graphHandler.scrollOnMove = false;
	this.graphHandler.scaleGrid = true;
	
	// 禁掉拖动线时如果没有目标节点，自动复制当前节点做为目标节点，
	// 引起多余的操作
	this.connectionHandler.setCreateTarget(false);

	// Sets the style to be used when an elbow edge is double clicked
	// this.alternateEdgeStyle = 'vertical';
	// this.alternateEdgeStyle =
	// 'edgeStyle=elbowEdgeStyle;elbow=horizontal;strokeColor=#007FFF';
	this.configure();
	
	// Disables built-in connection starts except shift is pressed when hovering the cell
	this.connectionHandler.isValidSource = function(cell, me)
	{
		return mxConnectionHandler.prototype.isValidSource.apply(this, arguments) &&
			((urlParams['connect'] != '2' && urlParams['connect'] != null) ||
			mxEvent.isShiftDown(me.getEvent()));
	};

	// Sets the style to be used when an elbow edge is double clicked
	this.alternateEdgeStyle = 'vertical';
	
	if (stylesheet == null) {
		this.loadStylesheet();
	}
	

	// Changes color of move preview for black backgrounds
	this.graphHandler.createPreviewShape = function(bounds)
	{
		this.previewColor = (this.graph.background == '#000000') ? 'white' : mxGraphHandler.prototype.previewColor;
		
		return mxGraphHandler.prototype.createPreviewShape.apply(this, arguments);
	};
	
	// Handles parts of cells by checking if part=1 is in the style and returning the parent
	// LATER: Handle recursive parts
	this.graphHandler.getCells = function(initialCell)
	{
	    var cells = mxGraphHandler.prototype.getCells.apply(this, arguments);

	    for (var i = 0; i < cells.length; i++)
	    {
			var state = this.graph.view.getState(cells[i]);
			var style = (state != null) ? state.style : this.graph.getCellStyle(cells[i]);
	    	
			if (mxUtils.getValue(style, 'part', false))
			{
		        var parent = this.graph.model.getParent(cells[i]);
	
		        if (this.graph.model.isVertex(parent))
		        {
		            cells[i] = parent;
		        }
			}
	    }

	    return cells;
	};
	// Handles parts of cells when cloning the source for new connections
	this.connectionHandler.createTargetVertex = function(evt, source)
	{
		var state = this.graph.view.getState(source);
		var style = (state != null) ? state.style : this.graph.getCellStyle(source);
    	
		if (mxUtils.getValue(style, 'part', false))
		{
	        var parent = this.graph.model.getParent(source);

	        if (this.graph.model.isVertex(parent))
	        {
	        	source = parent;
	        }
		}
		
		return mxConnectionHandler.prototype.createTargetVertex.apply(this, arguments);
	};
	

	// overload
	mxGraphModel.prototype.createId = function() {
		var a = this.nextId;
		this.nextId = Math.uuid();
		return a;
	};

	// Creates rubberband selection
	var rubberband = new mxRubberband(this);

	this.getRubberband = function() {
		return rubberband;
	};
	
	 // Workaround for Firefox where first mouse down is received
    // after tap and hold if scrollbars are visible, which means
    // start rubberband immediately if no cell is under mouse.
    var isForceRubberBandEvent = rubberband.isForceRubberbandEvent;
    rubberband.isForceRubberbandEvent = function(me)
    {
    	return isForceRubberBandEvent.apply(this, arguments) ||
    		(mxUtils.hasScrollbars(this.graph.container) && mxClient.IS_FF &&
    		mxClient.IS_WIN && me.getState() == null && mxEvent.isTouchEvent(me.getEvent()));
    };

	// Shows hand cursor while panning
	this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this,
					function() {
						this.container.style.cursor = 'pointer';
					}));

	this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this,
					function() {
						this.container.style.cursor = 'default';
					}));
	 // Forces panning for middle and right mouse buttons
	var panningHandlerIsForcePanningEvent = this.panningHandler.isForcePanningEvent;
	this.panningHandler.isForcePanningEvent = function(me)
	{
		return panningHandlerIsForcePanningEvent.apply(this, arguments) ||
			(mxEvent.isMouseEvent(me.getEvent()) && (mxEvent.isRightMouseButton(me.getEvent()) ||
			mxEvent.isMiddleMouseButton(me.getEvent())));
	};

	this.popupMenuHandler.autoExpand = true;
	
	this.popupMenuHandler.isSelectOnPopup = function(me)
	{
		return mxEvent.isMouseEvent(me.getEvent());
	};
	 // Adds support for HTML labels via style. Note: Currently, only the Java
    // backend supports HTML labels but CSS support is limited to the following:
    // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
	this.isHtmlLabel = function(cell)
	{
		var state = this.view.getState(cell);
		var style = (state != null) ? state.style : this.getCellStyle(cell);
		
		return style['html'] == '1' || style['whiteSpace'] == 'wrap';
	};
	
	
	// HTML entities are displayed as plain text in wrapped plain text labels
	this.cellRenderer.getLabelValue = function(state)
	{
		var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
		
		if (state.view.graph.isHtmlLabel(state.cell))
		{
			if (state.style['html'] != 1)
			{
				result = mxUtils.htmlEntities(result, false);
			}
		}
		
		return result;
	};
	

	// Enables links if graph is "disabled" (ie. read-only)
	var click = this.click;
	this.click = function(me)
	{
		if (!this.isEnabled())
		{
			var cell = me.getCell();
			
			if (cell != null)
			{
				var link = this.getLinkForCell(cell);
				
				if (link != null)
				{
					window.open(link);
				}
			}
		}
		else
		{
			return click.apply(this, arguments);
		}
	};
	
	var getCursorForCell = this.getCursorForCell;
	this.getCursorForCell = function(cell)
	{
		if (!this.isEnabled())
		{
			var link = this.getLinkForCell(cell);
			
			if (link != null)
			{
				return 'pointer';
			}
		}
		else
		{
			return getCursorForCell.apply(this, arguments);
		}
	};
	
	// Allows all events through
	this.isEventSourceIgnored2 = function(evtName, me)
	{
		return false;
	};
	// Unlocks all cells
	this.isCellLocked = function(cell) {
		return false;
	};
	
	// Tap and hold on background starts rubberband for multiple selected
	// cells the cell associated with the event is deselected
	this.addListener(mxEvent.TAP_AND_HOLD, mxUtils.bind(this, function(sender, evt)
	{
		if (!mxEvent.isMultiTouchEvent(evt))
		{
			var me = evt.getProperty('event');
			var cell = evt.getProperty('cell');
			
			if (cell == null)
			{
				var pt = mxUtils.convertPoint(this.container,
						mxEvent.getClientX(me), mxEvent.getClientY(me));
				rubberband.start(pt.x, pt.y);
			}
			else if (this.getSelectionCount() > 1 && this.isCellSelected(cell))
			{
				this.removeSelectionCell(cell);
			}
			
			// Blocks further processing of the event
			evt.consume();
		}
	}));

	// On connect the target is selected and we clone the cell of the preview edge for insert
	this.connectionHandler.selectCells = function(edge, target)
	{
		this.graph.setSelectionCell(target || edge);
	};
	
	// Shows connection points only if cell not selected
	this.connectionHandler.constraintHandler.isStateIgnored = function(state, source)
	{
		return source && state.view.graph.isCellSelected(state.cell);
	};
	
	// Updates constraint handler if the selection changes
	this.selectionModel.addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
	{
		var ch = this.connectionHandler.constraintHandler;
		
		if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true))
		{
			ch.currentFocus = null;
			ch.constraints = null;
			ch.destroyIcons();
		}
		
		ch.destroyFocusHighlight();
	}));

	if (touchStyle)
	{
		this.initTouch();
	}
};

//
//	// Tap and hold brings up context menu.
//	// Tolerance slightly below graph tolerance is better.
//	this.connectionHandler.tapAndHoldTolerance = 16;
//
//	// Tap and hold on background starts rubberband on cell starts connecting
//	var connectionHandlerTapAndHold = this.connectionHandler.tapAndHold;
//	this.connectionHandler.tapAndHold = function(me, state) {
//		if (state == null) {
//			if (!this.graph.panningHandler.active) {
//				rubberband.start(me.getGraphX(), me.getGraphY());
//				this.graph.panningHandler.panningTrigger = false;
//			}
//		} else if (tapAndHoldStartsConnection) {
//			connectionHandlerTapAndHold.apply(this, arguments);
//		} else if (this.graph.isCellSelected(state.cell)
//				&& this.graph.getSelectionCount() > 1) {
//			this.graph.removeSelectionCell(state.cell);
//		}
//	};
//
//	if (touchStyle) {
//		this.initTouch();
//	}
//};

// Graph inherits from mxGraph
mxUtils.extend(Graph, mxGraph);

/**
 * Allows to all values in fit.
 */
Graph.prototype.minFitScale = null;

/**
 * Allows to all values in fit.
 */
Graph.prototype.maxFitScale = null;
Graph.prototype.currentEditingCell = null;

/**
 * Loads the stylesheet for this graph.
 */
Graph.prototype.loadStylesheet = function() {
	var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
	var dec = new mxCodec(node.ownerDocument);
	dec.decode(node, this.getStylesheet());

};

Graph.prototype.configure = function() {
	var node = mxUtils.load(CONFIG_PATH + '/wfgraph.xml').getDocumentElement();
	var dec = new mxCodec(node.ownerDocument);
	dec.decode(node, this);
};


Graph.prototype.selectVertices = function(c) {
	//this.selectCells(true, false, a)
	c = c || this.getDefaultParent();
	this.setSelectionCells(this.model.filterDescendants(mxUtils.bind(this,
					function(c) {
						return this.view.getState(c) != null
//								&& this.model.getChildCount(c) == 0
						&&c.value&&(c.value.tagName=="Start"||
						c.value.tagName=="End"||
						c.value.tagName=="Manual"||
						c.value.tagName=="Fork"||
						c.value.tagName=="Join"||
						c.value.tagName=="SubProcess"||
						c.value.tagName=="Code"||
						c.value.tagName=="CounterSignature")
								&& (this.model.isVertex(c))
					}), c))
	
};

/**
 * Overrides method to provide connection constraints for shapes.
 */
Graph.prototype.getAllConnectionConstraints = function(terminal, source)
{
	if (terminal != null)
	{
		var constraints = mxUtils.getValue(terminal.style, 'points', null);
		
		if (constraints != null)
		{
			// Requires an array of arrays with x, y (0..1) and an optional
			// perimeter (0 or 1), eg. points=[[0,0,1],[0,1,0],[1,1]]
			var result = [];
			
			try
			{
				var c = JSON.parse(constraints);
				
				for (var i = 0; i < c.length; i++)
				{
					var tmp = c[i];
					result.push(new mxConnectionConstraint(new mxPoint(tmp[0], tmp[1]), (tmp.length > 2) ? tmp[2] != '0' : true));
				}
			}
			catch (e)
			{
				// ignore
			}
			
			return result;
		}
		else
		{
			if (terminal.shape != null)
			{
				if (terminal.shape.stencil != null)
				{
					if (terminal.shape.stencil != null)
					{
						return terminal.shape.stencil.constraints;
					}
				}
				else if (terminal.shape.constraints != null)
				{
					return terminal.shape.constraints;
				}
			}
		}
	}

	return null;
};


/**
 * Inverts the elbow edge style without removing existing styles.
 */
Graph.prototype.flipEdge = function(edge) {
	if (edge != null) {
		var state = this.view.getState(edge);
		var style = (state != null) ? state.style : this.getCellStyle(edge);

		if (style != null) {
			var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
					mxConstants.ELBOW_HORIZONTAL);
			var value = (elbow == mxConstants.ELBOW_HORIZONTAL)
					? mxConstants.ELBOW_VERTICAL
					: mxConstants.ELBOW_HORIZONTAL;
			this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
		}
	}
};

/**
 * Sets the default edge for future connections.
 */
Graph.prototype.setDefaultEdge = function(cell)
{
	if (cell != null && this.getModel().isEdge(cell))
	{
		// Take a snapshot of the cell at the moment of calling
		var proto = this.getModel().cloneCell(cell);
		
		// Delete existing points
		if (proto.geometry != null)
		{
			proto.geometry.points = null;
		}
		
		// Delete entry-/exitXY styles
		var style = proto.getStyle();
		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_X, null);
		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_Y, null);
		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_X, null);
		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_Y, null);
		proto.setStyle(style);
		
		// Uses edge template for connect preview
		this.connectionHandler.createEdgeState = function(me)
		{
    		return this.graph.view.createState(proto);
	    };

	    // Creates new connections from edge template
	    this.connectionHandler.factoryMethod = function()
	    {
    		return this.graph.cloneCells([proto])[0];
	    };
	}
};

/**
 * Disables folding for non-swimlanes.
 */
Graph.prototype.isCellFoldable = function(cell)
{
	var state = this.view.getState(cell);
	var style = (state != null) ? state.style : this.getCellStyle(cell);
	
	return this.foldingEnabled && this.isContainer(cell) && style['collapsible'] != '0';
};

/**
 * Disables drill-down for non-swimlanes.
 */
Graph.prototype.isValidRoot = function(cell)
{
	return this.isContainer(cell);
};


/**
 * Disables drill-down for non-swimlanes.
 */
Graph.prototype.isValidDropTarget = function(cell)
{
	return this.isContainer(cell) || mxGraph.prototype.isValidDropTarget.apply(this, arguments);
};

/**
 * Disables drill-down for non-swimlanes.
 */
Graph.prototype.isContainer = function(cell)
{
	var state = this.view.getState(cell);
	var style = (state != null) ? state.style : this.getCellStyle(cell);
	
	if (this.isSwimlane(cell))
	{
		return style['container'] != '0';
	}
	else
	{
		return style['container'] == '1';
	}
};

/**
 * Overrides createGroupCell to set the group style for new groups to 'group'.
 */
 Graph.prototype.createGroupCell = function()
 {
 var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
 group.setStyle('group');	
 return group;
 };
/**
 * Overrides tooltips to show position and size
 */
Graph.prototype.getTooltipForCell = function(cell) {
	var tip = '';
	if(this.isSwimlane(cell)) return tip;
	//	
	// if (this.getModel().isVertex(cell))
	// { var setting=cell.value;
	// var name=setting.getAttribute('Name');
	// var displayName=setting.getAttribute('DisplayName');
	// tip+='活动名称:'+name+"\n";
	// tip+='显示名称:'+displayName+"\n";
	// }
	// else if (this.getModel().isEdge(cell))
	// {
	// tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments);
	// }
	if (cell.value && cell.value.nodeName) {
		var setting = cell.value;
		var name = setting.getAttribute('Name');
		var displayName = setting.getAttribute('DisplayName');
		tip += '名称:' + name + "\n";
		tip += '显示名称:' + displayName + "\n";
	}
	return tip;
};

/**
 * Returns the label for the given cell.
 */
Graph.prototype.convertValueToString = function(cell) {
	if (cell.value != null && typeof(cell.value) == 'object') {
		return cell.value.getAttribute('DisplayName');
	}

	return mxGraph.prototype.convertValueToString.apply(this, arguments);
};

/**
 * Handles label changes for XML user objects.
 */
Graph.prototype.cellLabelChanged = function(cell, value, autoSize) {
	if (cell.value != null && typeof(cell.value) == 'object') {
		var tmp = cell.value.cloneNode(true);
		tmp.setAttribute('DisplayName', value);
		value = tmp;
	}

	mxGraph.prototype.cellLabelChanged.apply(this, arguments);
};

/**
 * Sets the link for the given cell.
 */
Graph.prototype.setLinkForCell = function(cell, link) {
	var value = null;

	if (cell.value != null && typeof(cell.value) == 'object') {
		value = cell.value.cloneNode(true);
	} else {
		var doc = mxUtils.createXmlDocument();

		value = doc.createElement('UserObject');
		value.setAttribute('label', cell.value);
	}

	if (link != null && link.length > 0) {
		value.setAttribute('link', link);
	} else {
		value.removeAttribute('link');
	}

	this.model.setValue(cell, value);
};

/**
 * Returns the link for the given cell.
 */
Graph.prototype.getLinkForCell = function(cell) {
	if (cell.value != null && typeof(cell.value) == 'object') {
		return cell.value.getAttribute('link');
	}

	return null;
};

/**
 * Customized graph for touch devices.
 */
Graph.prototype.initTouch = function() {
	// Disables new connections via "hotspot"
	this.connectionHandler.marker.isEnabled = function() {
		return this.graph.connectionHandler.first != null;
	};

	// Hides menu when editing starts
	this.addListener(mxEvent.START_EDITING, function(sender, evt) {
				this.panningHandler.hideMenu();
			});

	// Context menu for touchstyle
	var showMenu = false;
	var menuCell = null;

	// Checks if native hit detection did not return anything and does custom
	// hit detection for edges to take into account the tolerance
	this.updateMouseEvent = function(me) {
		mxGraph.prototype.updateMouseEvent.apply(this, arguments);

		if (me.getState() == null) {
			var cell = this.getCellAt(me.graphX, me.graphY);

			if (this.getModel().isEdge(cell)) {
				me.state = this.view.getState(cell);

				if (me.state != null && me.state.shape != null) {
					this.container.style.cursor = me.state.shape.node.style.cursor;
				}
			}
		}

		if (me.getState() == null) {
			this.container.style.cursor = 'default';
		}
	};

	// Handles popup menu on touch devices (tap selected cell)
	this.fireMouseEvent = function(evtName, me, sender) {
		if (evtName == mxEvent.MOUSE_DOWN) {
			if (!this.panningHandler.isMenuShowing()) {
				menuCell = me.getCell();
				showMenu = (menuCell != null)
						? this.isCellSelected(menuCell)
						: this.isSelectionEmpty();
			} else {
				showMenu = false;
				menuCell = null;
			}
		} else if (evtName == mxEvent.MOUSE_UP) {
			if (showMenu && !this.isEditing()) {
				if (!this.panningHandler.isMenuShowing()) {
					var x = mxEvent.getClientX(me.getEvent());
					var y = mxEvent.getClientY(me.getEvent());

					this.panningHandler.popup(x + 16, y, menuCell, me
									.getEvent());
				}

				showMenu = false;
				menuCell = null;
				me.consume();

				return;
			}

			showMenu = false;
			menuCell = null;
		}

		mxGraph.prototype.fireMouseEvent.apply(this, arguments);

		if (evtName == mxEvent.MOUSE_MOVE && me.isConsumed()) {
			showMenu = false;
			menuCell = null;
		}
	};
};
/**
 * 创建转移时初始化Transition.
 * 
 * @param {}
 *            a
 * @param {}
 *            b
 * @param {}
 *            c
 * @param {}
 *            d
 * @param {}
 *            e
 * @param {}
 *            f
 * @return {}
 */
Graph.prototype.insertEdge = function(a, b, c, d, e, f) {
	var transObj = new WfModel().getWorkflowTransitionObject();
	transObj.setAttribute('name', c || "");
	var edge=this.createEdge(a, b, transObj, d, e, f);
	edge=this.addEdge(edge, a, d, e);	
	return this.increaseTransitionNameSerialID(edge);
};

Graph.prototype.getUrl = function() {
	var url = location.href;
	var lastIndex = url.lastIndexOf("/");
	return url.substring(0, lastIndex).replace('EW/WorkflowDesigner', '');
};

Graph.prototype.travelCells = function(cellHandler, includeNonBizNode) {
	var q = [this.getDefaultParent()];

	while (q.length) {
		var cell = q.pop();
		var isBizNode = cell.value && cell.value.nodeName
				&& cell.value.getAttribute('IsBizNode') == 'true';
		if ((isBizNode || includeNonBizNode) && cellHandler(cell) === false)
			return;
		if (isBizNode) // bizNode does not have sub-cells
			continue;
		for (var i = cell.getChildCount() - 1; i >= 0; i--)
			q.push(cell.getChildAt(i));
	}
}

Graph.prototype.getActivities = function() {
	var activities = [];
	this.travelCells(function(cell) {
				if (cell.vertex)
					activities.push(cell);
			});
	return activities;
};

Graph.prototype.getTransitions = function() {
	var transitions = [];
	this.travelCells(function(cell) {
				if (cell.edge)
					transitions.push(cell);
			});
	return transitions;
};
Graph.prototype.increaseTransitionNameSerialID = function(edge) {
	this.getModel().beginUpdate();
	try {
		var initName = 'Transition';
		var initDisplayName = '转移';
		var existCell = [];
		for (var j = 1;; j++) {
			existCell = [];
			var compareName = initName + j;
			var compareDisplayName = initDisplayName + j;
			this.travelCells(function(cell) {
						if (cell.edge
								&& cell.value
								&& compareName == cell.value
										.getAttribute('Name'))
							existCell.push(cell);
					});
			if (existCell.length == 0) {
				edge.setAttribute('Name', compareName);
				edge.setAttribute('DisplayName', compareDisplayName);
				break;
			}
		}
	} finally {
		this.getModel().endUpdate();
	}
	return edge;
};
Graph.prototype.setDefaultFormName=function(cells){
 if (cells != null) {
		this.getModel().beginUpdate();
		try {
			for (var i = 0; i < cells.length; i++) {
				var cell = cells[i];
				var value = cell.value;
				var tagName=value.tagName;
				if(tagName=='Start'||tagName=='Manual'||tagName=='Code'){
					var workflow=this.getWorkflow();
					var forms = workflow.getElementsByTagName('Form');
					var defaultFormName="";
					if(forms.length > 0)
						defaultFormName=forms[0].getAttribute('Value');
					if (defaultFormName)
						cell.setAttribute('Form', defaultFormName);
				}
			}
		} finally {
			this.getModel().endUpdate();
		}
	}
		return cells;
		};
Graph.prototype.increaseCellNameSerialID=function(cells){
    if (cells != null) {
		this.getModel().beginUpdate();
		try {
			for (var i = 0; i < cells.length; i++) {
				var cell = cells[i];
				var value = cell.value;
				//得到图元的初始名称和显示名称
				var tagName=value.tagName;
				var initName = value.getAttribute('Name');
				var initKey=initName.replace(tagName,'');
				initName=tagName;
				var initDisplayName= value.getAttribute('DisplayName');
				initDisplayName=initDisplayName.replace(initKey,'');
				var existCell = [];
				for (var j = 1;; j++) {
					existCell = [];
					var compareName = initName + j;
					var compareDisplayName=initDisplayName+j;
					this.travelCells(function(cell) {
								if (cell.value
										&& compareName == cell.value
												.getAttribute('Name'))
									existCell.push(cell);
							},true);
					if (existCell.length == 0) {
						cell.setAttribute('Name', compareName);
						cell.setAttribute('DisplayName', compareDisplayName);
						break;
					}
				}
			}
		} finally {
			this.getModel().endUpdate();
		}
	}
		return cells;
};

Graph.prototype.getWorkflow = function() {
	return this.getModel().getCell('0').value;
};

(function()
		{
			/**
			 * HTML in-place editor
			 */
			mxCellEditor.prototype.isContentEditing = function()
			{
				return this.text2 != null && this.text2.style.display != 'none';
			};
			
			/**
			 * HTML in-place editor
			 */
			mxCellEditor.prototype.toggleViewMode = function()
			{
				if (this.text2 != null)
				{
					var tmp = this.saveSelection();
					
					if (this.textarea.style.display == 'none')
					{
						var content = this.text2.innerHTML.replace(/\n/g, '');
						
						if (this.textarea.value != content)
						{
							this.textarea.value = content;
							this.setModified(true);
						}
						
						this.textarea.style.display = 'block';
						this.text2.style.display = 'none';
						this.textarea.focus();
					}
					else
					{
						var content = this.textarea.value.replace(/\n/g, '<br/>');
						
						if (this.text2.innerHTML != content)
						{
							this.text2.innerHTML = content;
							this.setModified(true);
						}
						
						this.text2.style.display = '';
						this.textarea.style.display = 'none';
						this.text2.focus();
					}
				
					if (this.switchSelectionState != null)
					{
						this.restoreSelection(this.switchSelectionState);
					}
					
					this.switchSelectionState = tmp;
				}
			};
			
			var mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update;
			mxConstraintHandler.prototype.update = function(me, source)
			{
				if (!mxEvent.isAltDown(me.getEvent()))
				{
					mxConstraintHandlerUpdate.apply(this, arguments);
				}
				else
				{
					this.reset();
				}
			};

			/**
			 * Creates the keyboard event handler for the current graph and history.
			 */
			mxCellEditor.prototype.saveSelection = function()
			{
			    if (window.getSelection)
			    {
			        sel = window.getSelection();
			        
			        if (sel.getRangeAt && sel.rangeCount)
			        {
			            var ranges = [];
			            
			            for (var i = 0, len = sel.rangeCount; i < len; ++i)
			            {
			                ranges.push(sel.getRangeAt(i));
			            }
			            
			            return ranges;
			        }
			    }
			    else if (document.selection && document.selection.createRange)
			    {
			        return document.selection.createRange();
			    }
			    
			    return null;
			};

			/**
			 * Creates the keyboard event handler for the current graph and history.
			 */
			mxCellEditor.prototype.restoreSelection = function(savedSel)
			{
			    if (savedSel)
			    {
			        if (window.getSelection)
			        {
			            sel = window.getSelection();
			            sel.removeAllRanges();
			            
			            for (var i = 0, len = savedSel.length; i < len; ++i)
			            {
			                sel.addRange(savedSel[i]);
			            }
			        }
			        else if (document.selection && savedSel.select)
			        {
			            savedSel.select();
			        }
			    }
			};

			if ('contentEditable' in document.documentElement)
			{
				/**
				 * Keypress starts immediate editing on selection cell
				 */
				var editorUiInit = EditorUi.prototype.init;
				EditorUi.prototype.init = function()
				{
					editorUiInit.apply(this, arguments);

					mxEvent.addListener(this.editor.graph.container, 'keypress', mxUtils.bind(this, function(evt)
					{
						// KNOWN: Focus does not work if label is empty in quirks mode
						if (!this.editor.graph.isEditing() && !this.editor.graph.isSelectionEmpty() && evt.which !== 0 &&
							!mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))
						{
							this.editor.graph.escape();
							this.editor.graph.startEditing();

							if (mxClient.IS_FF)
							{
								var ce = this.editor.graph.cellEditor;

								// Initial keystroke is lost in FF
								if (ce.textarea.style.display != 'none')
								{
									ce.textarea.value = String.fromCharCode(evt.which);
								}
								else if (ce.text2 != null)
								{
									ce.text2.innerHTML = String.fromCharCode(evt.which);
						            var range = document.createRange();
						            range.selectNodeContents(ce.text2);
						            range.collapse(false);
						            
						            var selection = window.getSelection();
						            selection.removeAllRanges();
						            selection.addRange(range);
								}
							}
						}
					}));
				};

				/**
				 * HTML in-place editor
				 */
				var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
				mxCellEditor.prototype.startEditing = function(cell, trigger)
				{
					this.switchSelectionState = null;
					
					// First run cannot set display before supercall because textarea is lazy created
					// Lazy instantiates textarea to save memory in IE
					if (this.textarea == null)
					{
						this.init();
					}
					
					var state = this.graph.view.getState(cell);
			
					if (state != null && state.style['html'] == 1)
					{
						this.textarea.style.display = 'none';
					}
					else
					{
						this.textarea.style.display = 'block';
					}
			
					mxCellEditorStartEditing.apply(this, arguments);
			
					if (this.textarea.style.display == 'none')
					{
						this.text2 = document.createElement('div');
						this.text2.className = 'geContentEditable';
						this.text2.innerHTML = this.textarea.value.replace(/\n/g, '<br/>');
						
						// Invokes stop editing and escape hooks
						mxEvent.addListener(this.text2, 'keydown', mxUtils.bind(this, function(evt)
						{
							if (!mxEvent.isConsumed(evt))
							{
								if (this.isStopEditingEvent(evt))
								{
									this.graph.stopEditing(false);
									mxEvent.consume(evt);
								}
								else if (evt.keyCode == 27 /* Escape */)
								{
									this.graph.stopEditing(true);
									mxEvent.consume(evt);
								}
							}
						}));
						
						var style = this.text2.style;
										
						// Required to catch all events on the background in IE
						style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';

						style.cursor = 'text';
						style.outline = 'none';
						style.position = 'absolute';
						style.width = parseInt(this.textarea.style.width) + 'px';
						style.height = (parseInt(this.textarea.style.height) - 4) + 'px';
						style.left = parseInt(this.textarea.style.left) + 'px';
						style.top = parseInt(this.textarea.style.top) + 'px';
						style.fontFamily = this.textarea.style.fontFamily;
						style.fontWeight = this.textarea.style.fontWeight;
						style.textAlign = this.textarea.style.textAlign;
						style.fontSize = this.textarea.style.fontSize;
						style.color = this.textarea.style.color;
						
						// Matches line height correctionFactor in embedded HTML output
						if (state.text != null && state.text.node != null && state.text.node.ownerSVGElement != null)
						{
							var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(parseInt(this.textarea.style.fontSize) * mxConstants.LINE_HEIGHT) + 'px' :
								(mxConstants.LINE_HEIGHT * mxSvgCanvas2D.prototype.lineHeightCorrection);
							style.lineHeight = lh;
						}
						else
						{
							style.lineHeight = this.textarea.style.lineHeight;
						}
						
						this.graph.container.appendChild(this.text2);
						this.text2.contentEditable = true;
						this.text2.focus();

						if (this.isSelectText() && this.text2.innerHTML.length > 0)
						{
							document.execCommand('selectAll');
						}
					}
				};

				var mxCellEditorStopEditing = mxCellEditor.prototype.stopEditing;
				mxCellEditor.prototype.stopEditing = function(cancel)
				{
					if (this.text2 != null)
					{
						var content = this.text2.innerHTML;
						
						// Modified state also updated in code view action
						if (this.text2.style.display != 'none' && this.textarea.value != content)
						{
							this.textarea.value = content.replace(/\r\n/g, '').replace(/\n/g, '');
							this.setModified(true);
						}
						
						this.text2.parentNode.removeChild(this.text2);
						this.text2 = null;
					}
					
					mxCellEditorStopEditing.apply(this, arguments);
				};
				
				// Workaround for focusLost calls stopEditing when in HTML view
				var mxCellEditorFocusLost = mxCellEditor.prototype.focusLost;
				mxCellEditor.prototype.focusLost = function(evt)
				{
					if (this.text2 == null)
					{
						mxCellEditorFocusLost.apply(this, arguments);
					}
				};
			}

			function createHint()
			{
				var hint = document.createElement('div');
				hint.className = 'geHint';
				hint.style.whiteSpace = 'nowrap';
				hint.style.position = 'absolute';
				
				return hint;
			};
			
			/**
			 * Updates the hint for the current operation.
			 */
			mxGraphHandler.prototype.updateHint = function(me)
			{
				if (this.shape != null)
				{
					if (this.hint == null)
					{
						this.hint = createHint();
						this.graph.container.appendChild(this.hint);
					}

					var t = this.graph.view.translate;
					var s = this.graph.view.scale;
					var x = this.roundLength((this.bounds.x + this.currentDx) / s - t.x);
					var y = this.roundLength((this.bounds.y + this.currentDy) / s - t.y);
					
					this.hint.innerHTML = x + ', ' + y;

					this.hint.style.left = (this.shape.bounds.x + Math.round((this.shape.bounds.width - this.hint.clientWidth) / 2)) + 'px';
					this.hint.style.top = (this.shape.bounds.y + this.shape.bounds.height + 12) + 'px';
				}
			};

			/**
			 * Updates the hint for the current operation.
			 */
			mxGraphHandler.prototype.removeHint = function()
			{
				if (this.hint != null)
				{
					this.hint.parentNode.removeChild(this.hint);
					this.hint = null;
				}
			};

			/**
			 * Enables recursive resize for groups.
			 */
			mxVertexHandler.prototype.isRecursiveResize = function(state, me)
			{
				return !this.graph.isSwimlane(state.cell) && this.graph.model.getChildCount(state.cell) > 0 &&
					!mxEvent.isControlDown(me.getEvent()) && !this.graph.isCellCollapsed(state.cell);
			};

			/**
			 * Updates the hint for the current operation.
			 */
			mxVertexHandler.prototype.updateHint = function(me)
			{
				if (this.index != mxEvent.LABEL_HANDLE)
				{
					if (this.hint == null)
					{
						this.hint = createHint();
						this.state.view.graph.container.appendChild(this.hint);
					}

					if (this.index == mxEvent.ROTATION_HANDLE)
					{
						this.hint.innerHTML = this.currentAlpha + '&deg;';
					}
					else
					{
						var s = this.state.view.scale;
						this.hint.innerHTML = this.roundLength(this.bounds.width / s) + ' x ' + this.roundLength(this.bounds.height / s);
					}
					
					var rot = (this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0';
					var bb = mxUtils.getBoundingBox(this.bounds, rot);
					
					if (bb == null)
					{
						bb = this.bounds;
					}
					
					this.hint.style.left = bb.x + Math.round((bb.width - this.hint.clientWidth) / 2) + 'px';
					this.hint.style.top = (bb.y + bb.height + 12) + 'px';
				}
			};

			/**
			 * Updates the hint for the current operation.
			 */
			mxVertexHandler.prototype.removeHint = mxGraphHandler.prototype.removeHint;

			/**
			 * Updates the hint for the current operation.
			 */
			mxEdgeHandler.prototype.updateHint = function(me, point)
			{
				if (this.hint == null)
				{
					this.hint = createHint();
					this.state.view.graph.container.appendChild(this.hint);
				}

				var t = this.graph.view.translate;
				var s = this.graph.view.scale;
				var x = this.roundLength(point.x / s - t.x);
				var y = this.roundLength(point.y / s - t.y);
				
				this.hint.innerHTML = x + ', ' + y;
				this.hint.style.visibility = 'visible';
				
				if (this.isSource || this.isTarget)
				{
					if (this.constraintHandler.currentConstraint != null &&
						this.constraintHandler.currentFocus != null)
					{
						var pt = this.constraintHandler.currentConstraint.point;
						this.hint.innerHTML = '[' + Math.round(pt.x * 100) + '%, '+ Math.round(pt.y * 100) + '%]';
					}
					else if (this.marker.hasValidState())
					{
						this.hint.style.visibility = 'hidden';
					}
				}
				
				this.hint.style.left = Math.round(me.getGraphX() - this.hint.clientWidth / 2) + 'px';
				this.hint.style.top = (me.getGraphY() + 12) + 'px';
				
				if (this.hideEdgeHintThread != null)
				{
					window.clearTimeout(this.hideEdgeHintThread);
				}
				
				this.hideEdgeHintThread = window.setTimeout(mxUtils.bind(this, function()
				{
					if (this.hint != null)
					{
						this.hint.style.visibility = 'hidden';
					}
				}), 500);
			};

			/**
			 * Updates the hint for the current operation.
			 */
			mxEdgeHandler.prototype.removeHint = mxGraphHandler.prototype.removeHint;

			/**
			 * Defines the handles for the UI.
			 */
			var connectHandle = new mxImage(IMAGE_PATH + '/handle-connect.png', 26, 26);
			var mainHandle = new mxImage(IMAGE_PATH + '/handle-main.png', 17, 17);
			var secondaryHandle = new mxImage(IMAGE_PATH + '/handle-secondary.png', 17, 17);
			var rotationHandle = new mxImage(IMAGE_PATH + '/handle-rotate.png', 19, 21);

			mxVertexHandler.prototype.rotationHandleVSpacing = -20;

			mxConnectionHandler.prototype.connectImage = connectHandle;
			mxVertexHandler.prototype.handleImage = mainHandle;
			mxVertexHandler.prototype.secondaryHandleImage = secondaryHandle;
			mxEdgeHandler.prototype.handleImage = mainHandle;
			mxEdgeHandler.prototype.labelHandleImage = secondaryHandle;
			mxOutline.prototype.sizerImage = mainHandle;
			
			// Enables connections along the outline
			mxConnectionHandler.prototype.outlineConnect = true;
			mxEdgeHandler.prototype.manageLabelHandle = true;
			mxEdgeHandler.prototype.outlineConnect = true;
			mxCellHighlight.prototype.keepOnTop = true;
			
			// Pre-fetches images
			new Image().src = connectHandle.src;
			new Image().src = mainHandle.src;
			new Image().src = secondaryHandle.src;
			new Image().src = rotationHandle.src;
			
			var vertexHandlerCreateSizerShape = mxVertexHandler.prototype.createSizerShape;
			mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
			{
				this.handleImage = (index == mxEvent.ROTATION_HANDLE) ? rotationHandle : mxVertexHandler.prototype.handleImage;
				return vertexHandlerCreateSizerShape.apply(this, arguments);
			};
			
			// Requires callback to editorUi in edit link so override editorUi.init
			var editorUiInit3 = EditorUi.prototype.init;
			EditorUi.prototype.init = function()
			{
				editorUiInit3.apply(this, arguments);
				
				// Required for calling edit link action below
				var ui = this;
			
				/**
				 * Implements touch style
				 */
				if (touchStyle)
				{
					// Larger tolerance and grid for real touch devices
					if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)
					{
						mxShape.prototype.svgStrokeTolerance = 18;
						mxVertexHandler.prototype.tolerance = 12;
						mxEdgeHandler.prototype.tolerance = 12;
						Graph.prototype.tolerance = 12;
						
						mxVertexHandler.prototype.rotationHandleVSpacing = -24;
					}
						
					// One finger pans (no rubberband selection) must start regardless of mouse button
					mxPanningHandler.prototype.isPanningTrigger = function(me)
					{
						var evt = me.getEvent();
						
					 	return (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
					 		(mxEvent.isPopupTrigger(evt) && (me.getState() == null ||
					 		mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
					};
					
					// Don't clear selection if multiple cells selected
					var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
					mxGraphHandler.prototype.mouseDown = function(sender, me)
					{
						graphHandlerMouseDown.apply(this, arguments);
			
						if (mxEvent.isTouchEvent(me.getEvent()) && this.graph.isCellSelected(me.getCell()) &&
							this.graph.getSelectionCount() > 1)
						{
							this.delayedSelection = false;
						}
					};
				}
				
				var vertexHandlerMouseMove = mxVertexHandler.prototype.mouseMove;
				mxVertexHandler.prototype.mouseMove = function()
				{
					vertexHandlerMouseMove.apply(this, arguments);
					
					if (this.graph.graphHandler.first != null)
					{
						if (this.linkHint != null)
						{
							this.linkHint.style.display = 'none';
						}
						
						if (this.connectorImg != null)
						{
							this.connectorImg.style.display = 'none';
						}
						
						if (this.rotationShape != null && this.rotationShape.node != null)
						{
							this.rotationShape.node.style.display = 'none';
						}
					}
				};
				
				var vertexHandlerMouseUp = mxVertexHandler.prototype.mouseUp;
				mxVertexHandler.prototype.mouseUp = function()
				{
					vertexHandlerMouseUp.apply(this, arguments);
					
					if (this.connectorImg != null)
					{
						this.connectorImg.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
					}
					
					if (this.linkHint != null)
					{
						this.linkHint.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
					}
					
					// Shows rotation handle only if one vertex is selected
					if (this.rotationShape != null && this.rotationShape.node != null)
					{
						this.rotationShape.node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
					}
				};
				
				var vertexHandlerReset = mxVertexHandler.prototype.reset;
				mxVertexHandler.prototype.reset = function()
				{
					vertexHandlerReset.apply(this, arguments);
					
					// Shows rotation handle only if one vertex is selected
					if (this.rotationShape != null && this.rotationShape.node != null)
					{
						this.rotationShape.node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
					}
				};
				
				var vertexHandlerInit = mxVertexHandler.prototype.init;
				mxVertexHandler.prototype.init = function()
				{
					vertexHandlerInit.apply(this, arguments);
					var redraw = false;
					
					var update = mxUtils.bind(this, function()
					{
						if (this.connectorImg != null)
						{
							this.connectorImg.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
						}
						
						if (this.linkHint != null)
						{
							this.linkHint.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
						}

						// Shows rotation handle only if one vertex is selected
						if (this.rotationShape != null && this.rotationShape.node != null)
						{
							this.rotationShape.node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
						}
						
						if (this.specialHandle != null)
						{
							this.specialHandle.node.style.display = (this.graph.isEnabled() && this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ? '' : 'none';
						}
						
						this.redrawHandles();
					});
					
					this.selectionHandler = mxUtils.bind(this, function(sender, evt)
					{
						update();
					});
					
					this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.selectionHandler);
					
					this.changeHandler = mxUtils.bind(this, function(sender, evt)
					{
						this.updateLinkHint(this.graph.getLinkForCell(this.state.cell));
						update();
					});
					
					this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);

					if (this.graph.isEnabled() && (touchStyle || urlParams['connect'] == null || urlParams['connect'] == '2'))
					{
						// Only show connector image on one cell and do not show on containers
						if (showConnectorImg && this.graph.isCellConnectable(this.state.cell))
						{
							// Workaround for event redirection via image tag in quirks and IE8
							if (mxClient.IS_IE && !mxClient.IS_SVG)
							{
								// Workaround for PNG images in IE6
								if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')
								{
									this.connectorImg = document.createElement(mxClient.VML_PREFIX + ':image');
									this.connectorImg.setAttribute('src', connectHandle.src);
									this.connectorImg.style.borderStyle = 'none';
								}
								else
								{
									this.connectorImg = document.createElement('div');
									this.connectorImg.style.backgroundImage = 'url(' + connectHandle.src + ')';
									this.connectorImg.style.backgroundPosition = 'center';
									this.connectorImg.style.backgroundRepeat = 'no-repeat';
								}
								
								this.connectorImg.style.width = (connectHandle.width + 4) + 'px';
								this.connectorImg.style.height = (connectHandle.height + 4) + 'px';
								this.connectorImg.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
							}
							else
							{
								this.connectorImg = mxUtils.createImage(connectHandle.src);
								
								if (touchStyle)
								{
									this.connectorImg.style.width = '29px';
									this.connectorImg.style.height = '29px';
								}
								else
								{
									this.connectorImg.style.width = connectHandle.width + 'px';
									this.connectorImg.style.height = connectHandle.height + 'px';
								}
							}
							
							this.connectorImg.style.cursor = 'crosshair';
							this.connectorImg.style.position = 'absolute';
							this.connectorImg.setAttribute('title', mxResources.get('connect'));
							
							if (!(mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0))
							{
								mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
							}
							
							// Starts connecting on touch/mouse down
							mxEvent.addGestureListeners(this.connectorImg,
								mxUtils.bind(this, function(evt)
								{
									// FIXME: Use native event in isForceRubberband, isForcePanningEvent
									if (!mxEvent.isAltDown(evt) && !mxEvent.isPopupTrigger(evt))
									{
										this.graph.popupMenuHandler.hideMenu();
										this.graph.stopEditing(false);
										
										var pt = mxUtils.convertPoint(this.graph.container,
												mxEvent.getClientX(evt), mxEvent.getClientY(evt));
										this.graph.connectionHandler.start(this.state, pt.x, pt.y);
										this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
										this.graph.isMouseDown = true;
										
										mxEvent.consume(evt);
									}
								})
							);
			
							this.graph.container.appendChild(this.connectorImg);
							redraw = true;
						}
					}

					var link = this.graph.getLinkForCell(this.state.cell);
											
					if (link != null)
					{
						var label = link;
						var max = 60;
						var head = 36;
						var tail = 20;
						
						if (label.length > max)
						{
							label = label.substring(0, head) + '...' + label.substring(label.length - tail);
						}

						this.linkHint = createHint();
						this.linkHint.style.padding = '4px 10px 6px 10px';
						this.linkHint.style.fontSize = '90%';
						this.linkHint.style.opacity = '1';
						this.linkHint.style.filter = '';
						this.updateLinkHint(link);
						
						this.graph.container.appendChild(this.linkHint);
						redraw = true;
					}
					
					if (redraw)
					{
						this.redrawHandles();
					}
				};
				
				mxVertexHandler.prototype.updateLinkHint = function(link)
				{
					if (this.linkHint != null)
					{
						var label = link;
						var max = 60;
						var head = 36;
						var tail = 20;
						
						if (label.length > max)
						{
							label = label.substring(0, head) + '...' + label.substring(label.length - tail);
						}

						this.linkHint.innerHTML = '<a href="' + link + '" title="' + link + '" target="_blank">' + label + '</a>';
			
						if (this.graph.isEnabled())
						{
							var changeLink = document.createElement('img');
							changeLink.setAttribute('src', IMAGE_PATH + '/edit.gif');
							changeLink.setAttribute('title', mxResources.get('editLink'));
							changeLink.style.marginLeft = '10px';
							changeLink.style.marginBottom = '-1px';
							changeLink.style.cursor = 'pointer';
							this.linkHint.appendChild(changeLink);
							
							mxEvent.addListener(changeLink, 'click', mxUtils.bind(this, function(evt)
							{
								this.graph.setSelectionCell(this.state.cell);
								ui.actions.get('editLink').funct();
								mxEvent.consume(evt);
							}));
						}
					}
				};
				
				mxEdgeHandler.prototype.updateLinkHint = mxVertexHandler.prototype.updateLinkHint;
				
				var edgeHandlerInit = mxEdgeHandler.prototype.init;
				mxEdgeHandler.prototype.init = function()
				{
					edgeHandlerInit.apply(this, arguments);
					
					// Disables connection points
					this.constraintHandler.isEnabled = mxUtils.bind(this, function()
					{
						return this.state.view.graph.connectionHandler.isEnabled();
					});
					
					var update = mxUtils.bind(this, function()
					{
						if (this.linkHint != null)
						{
							this.linkHint.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
						}
						
						if (this.labelShape != null)
						{
							this.labelShape.node.style.display = (this.graph.isEnabled() && this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ? '' : 'none';
						}
					});

					this.selectionHandler = mxUtils.bind(this, function(sender, evt)
					{
						update();
					});
					
					this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.selectionHandler);
					
					this.changeHandler = mxUtils.bind(this, function(sender, evt)
					{
						this.updateLinkHint(this.graph.getLinkForCell(this.state.cell));
						update();
						this.redrawHandles();
					});
					
					this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);

					var link = this.graph.getLinkForCell(this.state.cell);
											
					if (link != null)
					{
						this.linkHint = createHint();
						this.linkHint.style.padding = '4px 10px 6px 10px';
						this.linkHint.style.fontSize = '90%';
						this.linkHint.style.opacity = '1';
						this.linkHint.style.filter = '';
						this.updateLinkHint(link);
						
						this.graph.container.appendChild(this.linkHint);
						this.redrawHandles();
					}
				};
			};
			
			// Disables connection points
			var connectionHandlerInit = mxConnectionHandler.prototype.init;
			
			mxConnectionHandler.prototype.init = function()
			{
				connectionHandlerInit.apply(this, arguments);
				
				this.constraintHandler.isEnabled = mxUtils.bind(this, function()
				{
					return this.graph.connectionHandler.isEnabled();
				});
			};

			var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles;
			mxVertexHandler.prototype.redrawHandles = function()
			{
				vertexHandlerRedrawHandles.apply(this);

				if (this.state != null && this.connectorImg != null)
				{
					var pt = new mxPoint();
					var s = this.state;
					
					// Top right for single-sizer
					if (mxVertexHandler.prototype.singleSizer)
					{
						pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
						pt.y = s.y - this.connectorImg.offsetHeight / 2;
					}
					else
					{
						var dx = (this.handleImage != null) ? this.handleImage.width : mxConstants.HANDLE_SIZE;
						pt.x = s.x + s.width + (dx + this.horizontalOffset + this.tolerance + this.connectorImg.offsetWidth) / 2;
						pt.y = s.y + s.height / 2;
					}
					
					var alpha = mxUtils.toRadians(mxUtils.getValue(s.style, mxConstants.STYLE_ROTATION, 0));
					
					if (alpha != 0)
					{
						var cos = Math.cos(alpha);
						var sin = Math.sin(alpha);
						
						var ct = new mxPoint(s.getCenterX(), s.getCenterY());
						pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
					}
					
					this.connectorImg.style.left = Math.round(pt.x - this.connectorImg.offsetWidth / 2) + 'px';
					this.connectorImg.style.top = Math.round(pt.y - this.connectorImg.offsetHeight / 2) + 'px';
				}
				
				if (this.state != null && this.linkHint != null)
				{
					var c = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
					var tmp = new mxRectangle(this.state.x, this.state.y - 22, this.state.width + 24, this.state.height + 22);
					var bb = mxUtils.getBoundingBox(tmp, this.state.style[mxConstants.STYLE_ROTATION] || '0', c);
					var rs = (bb != null) ? mxUtils.getBoundingBox(this.state, this.state.style[mxConstants.STYLE_ROTATION] || '0') : this.state;
					
					if (bb == null)
					{
						bb = this.state;
					}
					
					this.linkHint.style.left = Math.round(rs.x + (rs.width - this.linkHint.clientWidth) / 2) + 'px';
					this.linkHint.style.top = Math.round(bb.y + bb.height + this.verticalOffset / 2 +
							6 + this.state.view.graph.tolerance) + 'px';
				}
			};
			
			var vertexHandlerHideSizers = mxVertexHandler.prototype.hideSizers;
			mxVertexHandler.prototype.hideSizers = function()
			{
				vertexHandlerHideSizers.apply(this, arguments);
				
				if (this.connectorImg != null)
				{
					this.connectorImg.style.visibility = 'hidden';
				}
				
				if (this.linkHint != null)
				{
					this.linkHint.style.visibility = 'hidden';
				}
			};
			
			var vertexHandlerReset = mxVertexHandler.prototype.reset;
			mxVertexHandler.prototype.reset = function()
			{
				vertexHandlerReset.apply(this, arguments);
				
				if (this.connectorImg != null)
				{
					this.connectorImg.style.visibility = '';
				}
				
				if (this.linkHint != null)
				{
					this.linkHint.style.visibility = '';
				}
			};
			
			var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
			mxVertexHandler.prototype.destroy = function(sender, me)
			{
				vertexHandlerDestroy.apply(this, arguments);
				
				if (this.linkHint != null)
				{
					this.linkHint.parentNode.removeChild(this.linkHint);
					this.linkHint = null;
				}

				if (this.connectorImg != null)
				{
					this.connectorImg.parentNode.removeChild(this.connectorImg);
					this.connectorImg = null;
				}
				
				if (this.selectionHandler != null)
				{
					this.graph.getSelectionModel().removeListener(this.selectionHandler);
					this.selectionHandler = null;
				}
				
				if  (this.changeHandler != null)
				{
					this.graph.getModel().removeListener(this.cahngeHandler);
					this.changeHandler = null;
				}
			};
			
			var edgeHandlerRedrawHandles = mxEdgeHandler.prototype.redrawHandles;
			mxEdgeHandler.prototype.redrawHandles = function()
			{
				// Workaround for special case where handler
				// is reset before this which leads to a NPE
				if (this.marker != null)
				{
					edgeHandlerRedrawHandles.apply(this);
			
					if (this.state != null && this.linkHint != null)
					{
						var b = this.state;
						
						if (this.state.text != null && this.state.text.bounds != null)
						{
							b = new mxRectangle(b.x, b.y, b.width, b.height);
							b.add(this.state.text.bounds);
						}
						
						this.linkHint.style.left = Math.round(b.x + (b.width - this.linkHint.clientWidth) / 2) + 'px';
						this.linkHint.style.top = Math.round(b.y + b.height + 6 + this.state.view.graph.tolerance) + 'px';
					}
				}
			};
			
			var edgeHandlerHideSizers = mxEdgeHandler.prototype.hideSizers;
			mxEdgeHandler.prototype.hideSizers = function()
			{
				edgeHandlerHideSizers.apply(this, arguments);
				
				if (this.linkHint != null)
				{
					this.linkHint.style.visibility = 'hidden';
				}
			};
			
			var edgeHandlerReset = 	mxEdgeHandler.prototype.reset;
			mxEdgeHandler.prototype.reset = function()
			{
				edgeHandlerReset.apply(this, arguments);
				
				if (this.linkHint != null)
				{
					this.linkHint.style.visibility = '';
				}
			};
			
			var edgeHandlerDestroy = 	mxEdgeHandler.prototype.destroy;
			mxEdgeHandler.prototype.destroy = function(sender, me)
			{
				edgeHandlerDestroy.apply(this, arguments);
				
				if (this.linkHint != null)
				{
					this.linkHint.parentNode.removeChild(this.linkHint);
					this.linkHint = null;
				}

				if (this.selectionHandler != null)
				{
					this.graph.getSelectionModel().removeListener(this.selectionHandler);
					this.selectionHandler = null;
				}

				if  (this.changeHandler != null)
				{
					this.graph.getModel().removeListener(this.cahngeHandler);
					this.changeHandler = null;
				}
			};
		})();
